#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "objeto3d.h"

/**
	Le um arquivo obj simplificado e retorna a estrutura
	do modelo.
**/
struct Objeto3D lerObjeto3D(FILE *arquivo)
{
    struct Objeto3D objeto3D;

    /* Le numero de vertices */
    fscanf(arquivo, "%d\n", &objeto3D.nVertices);

    /* Aloca vertices */
    objeto3D.vertices = (struct Vetor3*)malloc(sizeof(struct Vetor3)*objeto3D.nVertices);

    /* Le vertices */
    for (int c = 0; c < objeto3D.nVertices; c++)
        fscanf(arquivo, "%f %f %f\n", &objeto3D.vertices[c].x, &objeto3D.vertices[c].y, &objeto3D.vertices[c].z);

    /* Le numero de normais */
    int nNormais;
    fscanf(arquivo, "%d\n", &nNormais);

    /* Aloca normais */
    struct Vetor3 *normais = (struct Vetor3*)malloc(sizeof(struct Vetor3)*nNormais);

    /* Le normais */
    for (int c = 0; c < nNormais; c++)
        fscanf(arquivo, "%f %f %f\n", &(normais[c].x), &(normais[c].y), &(normais[c].z));

    /* Le numero de faces */
    fscanf(arquivo, "%d\n", &objeto3D.nFaces);

    /* Aloca faces */
    objeto3D.faces = (struct Face*)malloc(sizeof(struct Face)*objeto3D.nFaces);

    /* Le faces */
    for (int c = 0; c < objeto3D.nFaces; c++)
    {
        struct Face& face = objeto3D.faces[c];

        /* Le indice dos vertices e normais */
        int n1, n2, n3;
        fscanf(arquivo, "%d,%d %d,%d %d,%d\n", &face.indicesVertices[0], &n1, 
                                               &face.indicesVertices[1], &n2,
                                               &face.indicesVertices[2], &n3);

        /* Transforma Indexacao iniciada em 1 para zero */
        face.indicesVertices[0] --;
        face.indicesVertices[1] --;
        face.indicesVertices[2] --;

        /* Atribui normais(indexacao iniciada em 1) */
        face.normais[0] = normais[n1-1];
        face.normais[1] = normais[n2-1];
        face.normais[2] = normais[n3-1];

        /* Seta vizinhos inexistentes */
        face.indicesVizinhos[0] = -1;
        face.indicesVizinhos[1] = -1;
        face.indicesVizinhos[2] = -1;

        /* Calcula eq do plano */
        struct Vetor3 v[4];

        for (int i=0; i<3; i++){
            v[i+1].x = objeto3D.vertices[face.indicesVertices[i]].x;
            v[i+1].y = objeto3D.vertices[face.indicesVertices[i]].y;
            v[i+1].z = objeto3D.vertices[face.indicesVertices[i]].z;
        }

        face.equacaoPlano.a = v[1].y*(v[2].z-v[3].z) + v[2].y*(v[3].z-v[1].z) + v[3].y*(v[1].z-v[2].z);
        face.equacaoPlano.b = v[1].z*(v[2].x-v[3].x) + v[2].z*(v[3].x-v[1].x) + v[3].z*(v[1].x-v[2].x);
        face.equacaoPlano.c = v[1].x*(v[2].y-v[3].y) + v[2].x*(v[3].y-v[1].y) + v[3].x*(v[1].y-v[2].y);
        face.equacaoPlano.d =-( v[1].x*(v[2].y*v[3].z - v[3].y*v[2].z) +
        v[2].x*(v[3].y*v[1].z - v[1].y*v[3].z) +
        v[3].x*(v[1].y*v[2].z - v[2].y*v[1].z) );
    }

    /* Libera memoria */
    free(normais);

    /* Calcula conectividade das arestas: baseado no artigo do Gamasutra */

	unsigned int p1i, p2i, p1j, p2j;
	unsigned int P1i, P2i, P1j, P2j;

	for(int i = 0; i < objeto3D.nFaces-1; i++)
		for(int j = i+1; j < objeto3D.nFaces; j++)
			for(int ki = 0; ki<3; ki++)
				if(!objeto3D.faces[i].indicesVizinhos[ki])
                {
					for(int kj = 0; kj<3; kj++)
                    {
						p1i = ki;
						p1j = kj;
						p2i = (ki+1)%3;
						p2j = (kj+1)%3;

						p1i = objeto3D.faces[i].indicesVertices[p1i];
						p2i = objeto3D.faces[i].indicesVertices[p2i];
						p1j = objeto3D.faces[j].indicesVertices[p1j];
						p2j = objeto3D.faces[j].indicesVertices[p2j];

						P1i = ((p1i+p2i)-abs(p1i-p2i))/2;
						P2i = ((p1i+p2i)+abs(p1i-p2i))/2;
						P1j = ((p1j+p2j)-abs(p1j-p2j))/2;
						P2j = ((p1j+p2j)+abs(p1j-p2j))/2;

                        /* Se sao vizinhos */
						if((P1i == P1j) && (P2i == P2j))
                        {
							objeto3D.faces[i].indicesVizinhos[ki] = j+1; 
							objeto3D.faces[j].indicesVizinhos[kj] = i+1;	  
						}
					}
				}

	return objeto3D;
}


struct Vetor3 desenhaObjeto3D(struct Objeto3D objeto3D)
{
    /* Calculo do centro de massa */
    struct Vetor3 centro;
    centro.x = 0;
    centro.y = 0;
    centro.z = 0;
    int total = 0;

	glBegin(GL_TRIANGLES);

	/* Percorre faces do objeto e desenha modelo */
	for (int f=0; f < objeto3D.nFaces; f++)
	{
		const struct Face& face = objeto3D.faces[f];
		
        for (int v=0; v<3; v++)
		{

            const struct Vetor3& vertice = objeto3D.vertices[face.indicesVertices[v]];

			/* Verifica se a normal esta definida */
			if (face.normais[v].x != 0 || face.normais[v].y != 0 || face.normais[v].z != 0)
            {
				glNormal3f(face.normais[v].x, face.normais[v].y, face.normais[v].z);
            }
            else
            {
                /* Define normal baseado no vertice */
                float n = sqrt((vertice.x * vertice.x) + 
                               (vertice.y * vertice.y) + 
                               (vertice.z * vertice.z));
                if (n != 0)
        			glNormal3f(vertice.x/n, vertice.y/n, vertice.z/n);
            }

            /* Seta coordenada de textura dependendo da direção da face */
            if (fabs(face.normais[v].x) > .5)
                glTexCoord2f(vertice.y/3., vertice.z/3.);
            else if (fabs(face.normais[v].y) > .5)
                glTexCoord2f(vertice.x/3., vertice.z/3.);
            else
                glTexCoord2f(vertice.x/3., vertice.y/3.);

            /* Determina vertice */
			glVertex3f(vertice.x, vertice.y, vertice.z);

            /* Soma para calculo do centro de massa */
			centro.x += vertice.x;
			centro.y += vertice.y;
			centro.z += vertice.z;
			total++;
		}

	}
	glEnd();

	/* Calcula e retorna centro de massa */
	centro.x /= total;
	centro.y /= total;
	centro.z /= total;

    return centro;
}

void desenhaSombra(struct Objeto3D objeto3D, float *posicaoLuz)
{
	unsigned int	i, j, k, jj;
	unsigned int	p1, p2;
	struct Vetor3	v1, v2;
	float			side;

	//set visual parameter
	for (i=0; i< objeto3D.nFaces;i++)
    {
		// chech to see if light is in front or behind the plane (face plane)
		side =	objeto3D.faces[i].equacaoPlano.a*posicaoLuz[0]+
				objeto3D.faces[i].equacaoPlano.b*posicaoLuz[1]+
				objeto3D.faces[i].equacaoPlano.c*posicaoLuz[2]+
				objeto3D.faces[i].equacaoPlano.d*posicaoLuz[3];
		if (side >0) objeto3D.faces[i].visivel = true;
				else objeto3D.faces[i].visivel = false;
	}

 	glDisable(GL_LIGHTING);
	glDepthMask(GL_FALSE);
	glDepthFunc(GL_LEQUAL);

	glEnable(GL_STENCIL_TEST);
	glColorMask(0, 0, 0, 0);
	glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

	// first pass, stencil operation decreases stencil value
	glFrontFace(GL_CCW);
	glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
	for (i=0; i < objeto3D.nFaces;i++)
    {
		if (objeto3D.faces[i].visivel)
			for (j = 0; j<3; j++)
            {
				k = objeto3D.faces[i].indicesVizinhos[j];
				if ((!k) || (!objeto3D.faces[k-1].visivel)){
					// here we have an edge, we must draw a polygon
					p1 = objeto3D.faces[i].indicesVertices[j];
					jj = (j+1)%3;
					p2 = objeto3D.faces[i].indicesVertices[jj];

					//calculate the length of the vector
					v1.x = (objeto3D.vertices[p1].x - posicaoLuz[0])*INFINITO;
					v1.y = (objeto3D.vertices[p1].y - posicaoLuz[1])*INFINITO;
					v1.z = (objeto3D.vertices[p1].z - posicaoLuz[2])*INFINITO;

					v2.x = (objeto3D.vertices[p2].x - posicaoLuz[0])*INFINITO;
					v2.y = (objeto3D.vertices[p2].y - posicaoLuz[1])*INFINITO;
					v2.z = (objeto3D.vertices[p2].z - posicaoLuz[2])*INFINITO;
					
					//draw the polygon
					glBegin(GL_TRIANGLE_STRIP);
						glVertex3f(objeto3D.vertices[p1].x,
									objeto3D.vertices[p1].y,
									objeto3D.vertices[p1].z);
						glVertex3f(objeto3D.vertices[p1].x + v1.x,
									objeto3D.vertices[p1].y + v1.y,
									objeto3D.vertices[p1].z + v1.z);

						glVertex3f(objeto3D.vertices[p2].x,
									objeto3D.vertices[p2].y,
									objeto3D.vertices[p2].z);
						glVertex3f(objeto3D.vertices[p2].x + v2.x,
									objeto3D.vertices[p2].y + v2.y,
									objeto3D.vertices[p2].z + v2.z);
					glEnd();
				}
			}
	}
	// second pass, stencil operation increases stencil value
	glFrontFace(GL_CW);
	glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
	for (i=0; i<objeto3D.nFaces; i++)
    {
		if (objeto3D.faces[i].visivel)
			for (j=0; j<3; j++)
            {
				k = objeto3D.faces[i].indicesVizinhos[j];
				if ((!k) || (!objeto3D.faces[k-1].visivel))
                {
					// here we have an edge, we must draw a polygon
					p1 = objeto3D.faces[i].indicesVertices[j];
					jj = (j+1)%3;
					p2 = objeto3D.faces[i].indicesVertices[jj];

					//calculate the length of the vector
					v1.x = (objeto3D.vertices[p1].x - posicaoLuz[0])*INFINITO;
					v1.y = (objeto3D.vertices[p1].y - posicaoLuz[1])*INFINITO;
					v1.z = (objeto3D.vertices[p1].z - posicaoLuz[2])*INFINITO;

					v2.x = (objeto3D.vertices[p2].x - posicaoLuz[0])*INFINITO;
					v2.y = (objeto3D.vertices[p2].y - posicaoLuz[1])*INFINITO;
					v2.z = (objeto3D.vertices[p2].z - posicaoLuz[2])*INFINITO;
					
					//draw the polygon
					glBegin(GL_TRIANGLE_STRIP);
						glVertex3f(objeto3D.vertices[p1].x,
									objeto3D.vertices[p1].y,
									objeto3D.vertices[p1].z);
						glVertex3f(objeto3D.vertices[p1].x + v1.x,
									objeto3D.vertices[p1].y + v1.y,
									objeto3D.vertices[p1].z + v1.z);

						glVertex3f(objeto3D.vertices[p2].x,
									objeto3D.vertices[p2].y,
									objeto3D.vertices[p2].z);
						glVertex3f(objeto3D.vertices[p2].x + v2.x,
									objeto3D.vertices[p2].y + v2.y,
									objeto3D.vertices[p2].z + v2.z);
					glEnd();
				}
			}
	}

	glFrontFace(GL_CCW);
	glColorMask(1, 1, 1, 1);

	//draw a shadowing rectangle covering the entire screen
	glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glStencilFunc(GL_NOTEQUAL, 0, 0xffffffff);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glPushMatrix();
	glLoadIdentity();
	glBegin(GL_TRIANGLE_STRIP);
		glVertex3f(-0.1f, 0.1f,-0.10f);
		glVertex3f(-0.1f,-0.1f,-0.10f);
		glVertex3f( 0.1f, 0.1f,-0.10f);
		glVertex3f( 0.1f,-0.1f,-0.10f);
	glEnd();
	glPopMatrix();
	glDisable(GL_BLEND);

	glDepthFunc(GL_LEQUAL);
	glDepthMask(GL_TRUE);
	glEnable(GL_LIGHTING);
	glDisable(GL_STENCIL_TEST);
	glShadeModel(GL_SMOOTH);
}

void killObjeto3D(struct Objeto3D objeto3D)
{
    free(objeto3D.vertices);
    free(objeto3D.faces);

    objeto3D.vertices = NULL;
    objeto3D.faces = NULL;

    objeto3D.nVertices = 0;
    objeto3D.nFaces = 0;
}
